﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

[assembly: CLSCompliant (true)]
namespace ChessModel
{
    [FlagsAttribute]
    public enum GameStatus
    {
        Go = 0, Temp = 1, Setup = 2,
        Check = 4, NoMoves = 8, Win = 16, Tie = 32, Loss = 64,
        Checkmate = 4 + 8 + 64, Stalemate = 8 + 32,
        Resign = 64 + 128, NoCaptures = 32 + 256, Repetitive = 32 + 512
    }


    /// <summary>Represent a game participant.</summary>
    public struct Player
    {
        private int index;

        public static readonly Player White = new Player (0);
        public static readonly Player Black = new Player (1);

        private static readonly Player[] Players = { White, Black };
        public static Player GetPlayer (int index) { return Players[index]; }

        private Player (int newIndex) { index = newIndex; }
        public Player Opponent { get { return Players[1 - index]; } }
        public int Index { get { return index; } }

        public static bool operator == (Player lhs, Player rhs) { return lhs.index == rhs.index; }
        public static bool operator != (Player lhs, Player rhs) { return lhs.index != rhs.index; }

        public override bool Equals (object j) { return ((Player) j).index == this.index; }
        public override int GetHashCode () { return index; }
        public override string ToString () { return index == 0? "White" : "Black"; }
    }


    ///// ///// ///// /////

    public delegate void GameCreatedEventHandler ();
    public delegate void StatusUpdateEventHandler ();
    public delegate void TokenRemoveEventHandler (int x, int y);
    public delegate void TokenPlaceEventHandler (int x, int y, int player, int tokenIndex);
    public delegate void MoveProcessEventHandler ();
    public delegate void UndoPreviewEventHandler ();
    public delegate void HiliteChangeEventHandler (int x, int y, bool newHilite);
    public delegate void HilitesClearEventHandler ();


    /// <summary>Represent a view to a chess game.</summary>
    /// <remarks>The public interface provides only a view to the game along with
    /// the event notifications that are available to the interface.
    /// </remarks>
    public class ChessView
    {
        public event GameCreatedEventHandler GameCreated;
        public event StatusUpdateEventHandler StatusUpdate;
        public event TokenRemoveEventHandler TokenRemove;
        public event TokenPlaceEventHandler TokenPlace;
        public event MoveProcessEventHandler MoveProcess;
        public event UndoPreviewEventHandler UndoPreview;
        public event HiliteChangeEventHandler HiliteChange;
        public event HilitesClearEventHandler HilitesClear;

        internal ChessView ()
        {
            Clear ();
        }

        public void OnGameCreated (ChessView newChessView)
        { if (GameCreated != null) GameCreated (); }

        public void OnStatusUpdate ()
        { if (StatusUpdate != null) StatusUpdate (); }

        public void OnTokenPlace (int x, int y, int player, int tokenIndex)
        { if (TokenPlace != null) TokenPlace (x, y, player, tokenIndex); }

        public void OnTokenRemove (int x, int y)
        { if (TokenRemove != null) TokenRemove (x, y); }

        public void OnPieceMoved ()
        { if (MoveProcess != null) MoveProcess (); }

        public void OnUndoPreview ()
        { if (UndoPreview != null) UndoPreview (); }

        public void OnHiliteChange (int x, int y, bool newHilite)
        { if (HiliteChange != null) HiliteChange (x, y, newHilite); }

        public void OnHilitesClear ()
        { if (HilitesClear != null) HilitesClear (); }


        public Chessboard Board { get; private set; }
        public PlyStack Plies;
        public PieceLists Pieces;
        public bool Same6Moves { get; internal set; }
        public bool NoCapturesFor50 { get; internal set; }

        public Type[] Promotions { get; internal set; }
        internal GameStatus firstStatus;

        internal void Clear ()
        {
            if (Pieces == null)
                Pieces = new PieceLists ();
            else
                Pieces.Clear ();

            if (Plies == null)
                Plies = new PlyStack (this);
            else
                Plies.Clear ();

            if (Board == null)
                Board = new Chessboard ();
            else
                Board.Clear ();

            if (Promotions == null || Promotions.Length != 2)
                Promotions = new Type[2];

            Promotions[0] = typeof (Queen);
            Promotions[1] = typeof (Queen);

            Same6Moves = false;
            NoCapturesFor50 = false;
            firstStatus = GameStatus.Setup;
        }


        public GameStatus Status
        { get { return Plies.Count > 0 ? Plies.Top.PlyStatus : firstStatus; } }

        public bool IsUnderway
        { get { return (Status & (GameStatus.Win | GameStatus.Tie | GameStatus.Loss | GameStatus.Setup)) == 0; } }

        public Player PlayerTurn
        { get { return Player.GetPlayer (Plies.Count % 2); } }

        public bool IsMovable (int x, int y)
        { return Board[x, y].IsPlayer (PlayerTurn); }

        public string CurrentPlayerPromotionName
        { get { return Promotions[PlayerTurn.Index].Name; } }


        public IEnumerable BoardPieces (Player player)
        {
            // No foreach here because promotions modify collection.
            for (int k = 0; k < Pieces[player].Count; ++k)
            {
                Piece piece = Pieces[player][k];
                if (!piece.IsCaptured)
                    yield return piece;
            }
        }


        internal bool isNoCapsIn50 ()
        {
            int repeat = 50;

            if (Plies.Count < repeat)
                return false;

            for (int k = Plies.Count - 1; k >= Plies.Count - repeat; --k)
                if (Plies[k].Captured != null || Plies[k].Moving is Pawn || Plies[k].Promotion != null)
                    return false;

            return true;
        }


        //TODO IsSame6Moves not quite rules of Hoyle
        internal bool isSame6Moves ()
        {
            int repeat = 6;

            if (Plies.Count < repeat + 2)
                return false;

            for (int k = Plies.Count - 1; k >= Plies.Count - repeat + 2; --k)
                if (Plies[k].From.X != Plies[k - 4].From.X || Plies[k].To.Y != Plies[k - 4].To.Y)
                    return false;

            return true;
        }


        internal void setStatus (GameStatus newStatus)
        {
            if (Plies.Count == 0)
                firstStatus = newStatus;
            else
                Plies.Top.PlyStatus = newStatus;
        }


        internal GameStatus calcStatus ()
        {
            bool threat = isKingThreatened (PlayerTurn);

            foreach (Piece piece in BoardPieces (PlayerTurn))
                foreach (Coordinate place in piece.LegalMoves)
                    if (threat)
                        return GameStatus.Check;
                    else
                        return GameStatus.Go;

            if (threat)
                return GameStatus.Checkmate;
            else
                return GameStatus.Stalemate;
        }


        internal bool isKingThreatened (Player kingPlayer)
        {
            foreach (Piece piece in BoardPieces (kingPlayer.Opponent))
                foreach (Coordinate move in piece.moves)
                    if (Board[move.X, move.Y] is King)
                        return true;

            return false;
        }


        public string[] GetStatus ()
        {
            ChessView game = this;
            string TurnText, Status1 = "", Status2 = "";

            GameStatus newStatus = game.Status;

            if (game.IsUnderway)
                TurnText = game.PlayerTurn.ToString ();
            else if ((newStatus & GameStatus.Setup) != 0)
                TurnText = "";
            else
                TurnText = "GAME OVER";

            if (newStatus == GameStatus.Check)
                Status1 = "Check!";
            else if ((newStatus & GameStatus.Win) != 0)
                Status1 = game.PlayerTurn.ToString () + " Wins!";
            else if ((newStatus & GameStatus.Loss) != 0)
            {
                if (newStatus == GameStatus.Checkmate)
                    Status1 = "Checkmate";
                else if ((newStatus & GameStatus.Resign) == GameStatus.Resign)
                    Status1 = "Resignation";
                Status2 = game.PlayerTurn.Opponent.ToString () + " Wins!";
            }
            else if ((newStatus & GameStatus.Tie) != 0)
            {
                Status1 = "Draw";
                if (newStatus == GameStatus.Stalemate)
                    Status2 = "Stalemate";
                else if (newStatus == GameStatus.NoCaptures)
                    Status2 = "No captures";
                else if (newStatus == GameStatus.Repetitive)
                    Status2 = "3 turns same";
            }
            else
                Status1 = "Go";

            string[] retVal = new string[3] { TurnText, Status1, Status2 };

            return retVal;
        }


        public void BeginShowMoves (int x, int y)
        {
            OnHilitesClear ();

            Piece piece = Board[x, y] as Piece;
            if (piece != null)
                foreach (Coordinate place in piece.LegalMoves)
                    OnHiliteChange (place.X, place.Y, true);
        }

        public void EndShowMoves ()
        {
            OnHilitesClear ();
        }

    }


    ///// ///// ///// ///// /////


    /// <summary>Represent a playable chess game.
    /// </summary>
    public class ChessGame : ChessView
    {
        public ChessGame ()
        {
            SetupStandard ();
        }

        /// <summary>
        /// Create a board with kings only.  More tokens may be added later.
        /// </summary>
        public ChessGame (int whiteKingX, int whiteKingY, int blackKingX, int blackKingY)
        {
            Setup (new King (Player.Black), blackKingX, blackKingY);
            Setup (new King (Player.White), whiteKingX, whiteKingY);
        }


        public void SetupKings (int whiteKingX, int whiteKingY, int blackKingX, int blackKingY)
        {
            Setup (new King (Player.Black), blackKingX, blackKingY);
            Setup (new King (Player.White), whiteKingX, whiteKingY);
            Clear ();
        }

        /// <summary>Create a board with standard setup.</summary>
        public void SetupStandard ()
        {
            Clear ();

            Setup (new King (Player.White), 4, 0);
            Setup (new Rook (Player.White), 0, 0);
            Setup (new Knight (Player.White), 1, 0);
            Setup (new Bishop (Player.White), 2, 0);
            Setup (new Queen (Player.White), 3, 0);
            Setup (new Bishop (Player.White), 5, 0);
            Setup (new Knight (Player.White), 6, 0);
            Setup (new Rook (Player.White), 7, 0);

            for (int x = 0; x < Board.XSize; ++x)
                Setup (new Pawn (Player.White), x, 1);

            for (int x = 0; x < Board.XSize; ++x)
                for (int y = 0; y < Board.YSize / 2; ++y)
                {
                    Piece piece = Board[x, y] as Piece;
                    if (piece != null)
                    {
                        Piece piece2 = piece.ConstructOppositePiece ();
                        Setup (piece2, x, Board.YSize - y - 1);
                    }
                }

            firstStatus = GameStatus.Go;

            OnGameCreated (this);
            OnStatusUpdate ();
        }

        /// <summary>Place a piece on the board.</summary>
        /// <param name="newPiece">Token to place.</param>
        /// <param name="newX">Initial X coordinate of piece.</param>
        /// <param name="newY">Initial Y coordinate of piece.</param>
        public void Setup (Piece newPiece, int newX, int newY)
        {
            newPiece.Game = (ChessView) this;
            newPiece.Place = new Coordinate (newX, newY);
            Board.squares[newX, newY] = newPiece;
            Pieces.Add (newPiece);
        }

        /// <summary>Assign promotion piece for next time pawn is promoted.
        /// </summary>
        /// <param name="newPieceName">Name of promotion piece.</param>
        public void SetPromotion (string newPieceName)
        {
            Player player = PlayerTurn;

            if (newPieceName.Equals (typeof (Queen).Name, StringComparison.OrdinalIgnoreCase))
                SetPromotion (player, typeof (Queen));
            else if (newPieceName.Equals (typeof (Rook).Name, StringComparison.OrdinalIgnoreCase))
                SetPromotion (player, typeof (Rook));
            else if (newPieceName.Equals (typeof (Bishop).Name, StringComparison.OrdinalIgnoreCase))
                SetPromotion (player, typeof (Bishop));
            else if (newPieceName.Equals (typeof (Knight).Name, StringComparison.OrdinalIgnoreCase))
                SetPromotion (player, typeof (Knight));
            else
                throw new ArgumentException ("Unknown promotion '" + newPieceName + "'");
        }

        /// <summary>Assign promotion piece for next time pawn is promoted.
        /// </summary>
        /// <param name="player">Player of promotion assignment.</param>
        /// <param name="newPromotion">Type of promotion piece.</param>
        public void SetPromotion (Player player, Type newPromotion)
        {
            if (newPromotion != typeof (Queen)
                && newPromotion != typeof (Rook)
                && newPromotion != typeof (Bishop)
                && newPromotion != typeof (Knight))
                throw new ArgumentException ("Invalid promotion piece");

            Promotions[player.Index] = newPromotion;
        }


        public string MakeMove (int fromX, int fromY, int toX, int toY)
        {
            if (Plies.Count == 0)
                if (firstStatus == GameStatus.Setup)
                {
                    if (isKingThreatened (Player.Black))
                        return "Cannot begin with opposing king threat";

                    firstStatus = calcStatus ();
                }

            if (!IsUnderway)
                return "Cannot move piece when game is complete";

            Piece piece = Board[fromX, fromY] as Piece;
            if (piece == null || piece.Player != PlayerTurn)
                return "Square not occupied by current player";

            if (Move (fromX, fromY, toX, toY))
                return "Illegal move";

            Ply ply = Plies.Top;

            OnTokenRemove (fromX, fromY);

            if (ply.IsPawnPromotion)
                OnTokenPlace (toX, toY, ply.Moving.Player.Index, ply.Promotion.Index);
            else
            {
                OnTokenPlace (toX, toY, ply.Moving.Player.Index, ply.Moving.Index);
                if (ply.IsCaptureEnPassant)
                    OnTokenRemove (toX, fromY);
                else if (ply.IsQueensideCastle)
                {
                    OnTokenRemove (0, fromY);
                    OnTokenPlace (toX + 1, toY, ply.Moving.Player.Index, Rook.StaticIndex);
                }
                else if (ply.IsKingsideCastle)
                {
                    OnTokenRemove (Board.YSize - 1, fromY);
                    OnTokenPlace (toX - 1, toY, ply.Moving.Player.Index, Rook.StaticIndex);
                }
            }

            OnPieceMoved ();
            OnStatusUpdate ();

            // Success.
            return null;
        }


        private bool Move (int fromX, int fromY, int toX, int toY)
        {
            Piece piece = Board[fromX, fromY] as Piece;

            Debug.Assert (piece != null || piece.Player == PlayerTurn);

            foreach (Coordinate move in piece.LegalMoves)
                if (move.X == toX && move.Y == toY)
                {
                    Plies.Push (piece, toX, toY);
                    Plies.Top.PlyStatus = calcStatus ();

                    if (IsUnderway)
                    {
                        Same6Moves = isSame6Moves ();
                        NoCapturesFor50 = isNoCapsIn50 ();
                    }

                    return false;
                }

            return true;
        }

        public string UndoMove ()
        {
            if (!IsUnderway || Plies.Count == 0)
                return "Cannot undo";

            Ply ply = Plies.Top;
            int playerIndex = ply.Moving.Player.Index;

            if (ply.Captured == null)
                OnTokenRemove (ply.To.X, ply.To.Y);
            else
            {
                int y;
                if (ply.IsCaptureEnPassant)
                {
                    y = ply.From.Y;
                    OnTokenRemove (ply.To.X, ply.To.Y);
                }
                else
                    y = ply.To.Y;

                OnTokenPlace (ply.To.X, y, ply.Captured.Player.Index, ply.Captured.Index);
            }

            int token;
            if (ply.Promotion != null)
                token = Pawn.StaticIndex;
            else
                token = ply.Moving.Index;

            OnTokenPlace (ply.From.X, ply.From.Y, playerIndex, token);

            if (ply.Moving is King)
            {
                if (ply.IsQueensideCastle)
                {
                    OnTokenPlace (0, ply.To.Y, playerIndex, Rook.StaticIndex);
                    OnTokenRemove (ply.To.X + 1, ply.To.Y);
                }
                else if (ply.IsKingsideCastle)
                {
                    OnTokenPlace (Board.XSize - 1, ply.To.Y, playerIndex, Rook.StaticIndex);
                    OnTokenRemove (ply.To.X - 1, ply.To.Y);
                }
            }

            OnUndoPreview ();
            Plies.Pop ();
            OnStatusUpdate ();

            return null;
        }


        public void DrawGame ()
        {
            if (!IsUnderway)
                throw new ArgumentException ("Cannot draw a completed game.");

            setStatus (Status | GameStatus.Tie);
            OnStatusUpdate ();
        }

        public void ResignGame ()
        {
            if (!IsUnderway)
                throw new ArgumentException ("Cannot resign a completed game.");

            setStatus (Status | GameStatus.Loss | GameStatus.Resign);
            OnStatusUpdate ();
        }
    }
}
